#include "arch_overflow.h"

#define EXT_EELL_OVER     ".esp"
#define TAM_EELL_OVER     128
#define TAM_CABECERA_OVER 8
#define TAM_TAG_SIG		  sizeof(unsigned int)	
#define TAM_TAG_ANT		  sizeof(unsigned int)	

ArchOverflow::ArchOverflow(std::string& nombre, unsigned int tam_bloque):ArchivoDatos(nombre, tam_bloque){
	std::string aux = nombre + EXT_EELL_OVER;
	espacios_libres = new ArchEspaciosLibres(aux,TAM_EELL_OVER);
	this->agregar_observador(Buffer::get_instance());
}

ArchOverflow::~ArchOverflow(){
	//Si el archivo esta abierto, lo cerramos
	this->cerrar();
	delete(espacios_libres);	
}

void ArchOverflow::set_anterior(Bloque* bloque,unsigned int ant){
	bloque->escribir((char*)&ant, TAM_TAG_SIG,TAM_TAG_ANT);
}

void ArchOverflow::set_siguiente(Bloque* bloque,unsigned int sig){
	bloque->escribir((char*)&sig, 0,TAM_TAG_SIG);	
}
	
void ArchOverflow::abrir() throw(ExcepAbriendoArch){
	//Si el archivo de datos esta abierto, arrojo excep
	if (this->fd != -1)
		throw ExcepAbriendoArch("Error - El archivo: " + this->get_nombre() + " ya estaba abierto.");
	else {
		std::string dest = "./DATA/" + this->get_nombre();
		this->fd = ManejadorArchivos::get_instance()->abrir_archivo(dest);
		if ( this->fd == -1)
			throw ExcepAbriendoArch (ManejadorErrores::get_msg_error(errno) + " Archivo: " + this->get_nombre());
		espacios_libres->abrir(); //arroja una excepcion en caso de no poder abrirlo
		espacios_libres->agregar_observador(Buffer::get_instance());
		this-> cant_bloques = get_cant_bloques_header();
	}	
}

unsigned int ArchOverflow::get_cant_bloques_header(){
	unsigned int cant;
	//Obtengo el bloque header del archivo
	Bloque* bloque_header = Buffer::get_instance()->get_bloque(*this, 0);
	bloque_header->leer((char*)&cant, 0, sizeof(unsigned int));
	delete (bloque_header);
	return cant;
}

void ArchOverflow::crear(std::string formato) throw(ExcepCreandoArch, ExcepSintaxisParser, ExcepBloqueTam){
	if(this->get_tam_bloque() < TAM_CABECERA_OVER)
		throw ExcepBloqueTam("Error - No se pudo crear el archivo. El tamaño del bloque es muy pequeno para la cabecera");
	else{
		try{
			Archivo::cargar_registro_base(formato);
			TipoStruct * reg = get_registro_base();
			if(reg->get_tamanio() == 0){
				throw ExcepSintaxisParser("Error - No se ingreso formato para el archivo");
			}
			std::string dest = "./DATA/" + this->get_nombre();
			this->fd = ManejadorArchivos::get_instance()->crear_archivo(dest);
			if ( this->fd == -1 )
				throw ExcepCreandoArch (ManejadorErrores::get_msg_error(errno) + " Archivo: " + this->get_nombre());
			espacios_libres->crear(); //arroja una excepcion en caso de no poder crearlo.
			espacios_libres->agregar_observador(Buffer::get_instance());
			//Crea el header, y le guarda el formato
			dest = this->get_nombre() + ".head";
			Header head(dest);
			head.crear();
			head.guardar(formato, this);
			head.cerrar();
			
			this->cant_bloques = 2;
			//creo el bloque 0 para guardar la cantidad de bloques existentes en el archivo
			Bloque* bloque_header = new Bloque(0, this->get_tam_bloque());
			bloque_header->escribir((char*)&this->cant_bloques, 0, sizeof(unsigned int));
			Buffer::get_instance()->guardar_bloque(*this, bloque_header);
			espacios_libres->append(0);
			delete bloque_header;
			//Creo el bloque 1 y lo guardo, lo vamos a usar para primera entrada de la tabla
			Bloque* bloque = new Bloque(1, this->get_tam_bloque());
			set_siguiente(bloque,1); //se apunta a si mismo
			set_anterior(bloque,1);
			espacios_libres->append(bloque->get_espacio_libre());
			Buffer::get_instance()->guardar_bloque(*this, bloque);
			delete (bloque);
			
			
		}
		catch(ExcepSintaxisParser &e){
			throw e; //para hacerlo explicito
		}
	}	
}

void ArchOverflow::cerrar() {
	if(fd != -1){
		set_cant_bloques_header(this->cant_bloques);
		this->set_cambio();
		this->avisar_observadores(NULL);
		ManejadorArchivos::get_instance()->cerrar_archivo(this->fd);
		espacios_libres->cerrar();
		this->fd = -1;
	}
}

void ArchOverflow::ingresar(int pos,TipoStruct * reg) throw(ExcepArchCerrado,ExcepRegExistente, ExcepBloqueTam){
	
	unsigned int tam = this->get_tamanio_serializado(reg);
	//Verifico que el registro tenga menor tamanio que el bloque
	if( tam <= this->get_tam_bloque() - TAM_CABECERA_OVER){
		this->poner_reg_en_bloque(pos,reg);
	}
	else
		throw ExcepBloqueTam("Error - El dato a ingresar es mas grande que el tamano del bloque de " + this->get_nombre() + ". ");
	
}

unsigned int ArchOverflow::get_anterior(Bloque* bloque_actual){
	unsigned  int result = 0;
	//Leo en el bloque desde la pos 2(donde termina la pos del siguiente), la pos del reg anterior
	bloque_actual->leer((char*)&result,TAM_TAG_SIG,TAM_TAG_ANT);
	return (result);
}

unsigned int ArchOverflow::get_siguiente(Bloque* bloque_actual){
	unsigned int result = 0;
	//Leo desde el bloque en la pos 0 ,la pos del siguiente reg
	bloque_actual->leer((char*)&result, 0, TAM_TAG_SIG);
	return (result);
}		

int ArchOverflow::quitar(unsigned int pos, TipoStruct * reg) throw(ExcepArchCerrado){
	Bloque * bloque_actual = NULL;
	bool encontro = false;
	unsigned int size;
	TipoStruct* reg_base ;
	int retorno = SIN_CAMBIOS;	
	unsigned int pos_actual=0;
	while(!encontro){
		bloque_actual = Buffer::get_instance()->get_bloque(*this,pos);
		reg_base = (TipoStruct*)this->get_registro_base()->clonar();
		encontro = bloque_actual->buscar_por_clave_desde(TAM_TAG_SIG + TAM_TAG_ANT,&((*reg)[0]),reg_base, pos_actual, size);
		if (encontro){
			retorno = sacar_reg_bloque(bloque_actual,NULL,pos_actual, size);
			Buffer::get_instance()->guardar_bloque(*this,bloque_actual);
		}
		else{
			pos=get_siguiente(bloque_actual);
			if(pos == bloque_actual->get_num_bloque())
				encontro = true; //estamos en fin de lista y no se encontro el bloque,sale,no deberia pasar esto
		}
		delete reg_base;
		delete bloque_actual;		
	}
	return retorno;		
}
		
void ArchOverflow::consultar_todos(unsigned int pos, std::vector<TipoStruct*> &resultado)
									 throw(ExcepLeerBloque){
	resultado.clear();
	std::vector<TipoStruct*> temp;
	bool hay_elementos = true;
	Bloque* bloque = NULL;
	TipoStruct * reg_base = (TipoStruct*)this->get_registro_base()->clonar();
	unsigned int pos_actual = pos;
	while(hay_elementos){
		bloque = Buffer::get_instance()->get_bloque(*this,pos_actual);
		bloque->get_registros_desde(reg_base, temp, TAM_TAG_SIG + TAM_TAG_ANT);
		unsigned int sig = get_siguiente(bloque);
		//quiere decir que hay mas elementos en otro bloque
		if(sig != bloque->get_num_bloque())
			pos_actual = sig; //le asigno la pos del prox bloque a leer
		else //es el ultimo
			hay_elementos = false;
		this->concatenar(resultado,temp);	
		delete bloque;
	}
	delete reg_base;
}
 		
void ArchOverflow::concatenar(std::vector<TipoStruct*> &v1, std::vector<TipoStruct*> &v2){
	std::vector<TipoStruct*>::iterator it = v2.begin();
	while (it != v2.end()) {
		TipoStruct* reg = (*it);	
		v1.push_back(reg);
		it++;
	}
	v2.clear();
}
 		
void ArchOverflow::eliminar() {
	//Elimino el archivo de espacios libres
	espacios_libres->eliminar();
	//Cierro el archivo
	this->cerrar();
	//Elimino el archivo de header
	std::string header = "./DATA/"+get_nombre()+".head";
	remove(header.c_str());
	//Lo Elimino
	std::string mi_ruta = "./DATA/"+get_nombre();
	remove(mi_ruta.c_str()); 
}
		
int ArchOverflow::sacar_reg_bloque(Bloque* bloque, TipoStruct * reg, unsigned int pos_reg, unsigned int tam_reg){
	//Saco el reg del bloque
	bloque->comprimir(pos_reg, tam_reg);
	//actualizo el arch de espacios libres.
	espacios_libres->set_valor(bloque->get_num_bloque(),bloque->get_espacio_libre());
	//lo devuelvo a disco despues de haber sacado el registro
	Buffer::get_instance()->guardar_bloque(*this,bloque);
			
	if( (bloque->get_espacio_libre() + TAM_TAG_ANT + TAM_TAG_SIG + 4) == bloque->get_size() ){
		unsigned int ant = get_anterior(bloque);
		unsigned int sig = get_siguiente(bloque);
		unsigned int num_bloque = bloque->get_num_bloque();
		//es el unico, hay un solo elemento en la lista
		if((sig == num_bloque) && (ant == num_bloque)){
			//saco el bloque porque queda vacio
			return SIN_ELEMENTOS;	
		}

		//si no es el primero ni el ultimo, estoy en el caso del medio
		if ((ant != num_bloque) && (sig != num_bloque)){
			Bloque* bloque_siguiente = Buffer::get_instance()->get_bloque(*this,sig);
			Bloque* bloque_anterior = Buffer::get_instance()->get_bloque(*this,ant);
			set_anterior(bloque_siguiente,ant);
			set_siguiente(bloque_anterior,sig);
			Buffer::get_instance()->guardar_bloque(*this,bloque_anterior);
			Buffer::get_instance()->guardar_bloque(*this,bloque_siguiente);
			delete bloque_siguiente;
			delete bloque_anterior;
			return SIN_CAMBIOS;
		}
		//es el primero y no el unico
		if((ant == num_bloque && sig!=num_bloque)){
			Bloque* bloque_siguiente = Buffer::get_instance()->get_bloque(*this,sig);
			set_anterior(bloque_siguiente,sig);
			Buffer::get_instance()->guardar_bloque(*this,bloque_siguiente);
			delete bloque_siguiente;
			return sig;	
		}
		//es el ultimo y no el unico
		if((sig == num_bloque) && (ant!=num_bloque)){
			Bloque* bloque_anterior = Buffer::get_instance()->get_bloque(*this,ant);
			set_siguiente(bloque_anterior,ant);
			Buffer::get_instance()->guardar_bloque(*this,bloque_anterior);
			delete bloque_anterior;
			return SIN_CAMBIOS;
		}
	}
	return SIN_CAMBIOS;
}			

bool poner_reg_en_bloque(Bloque* bloque, TipoStruct* reg){
	return true;
}

void ArchOverflow::poner_reg_en_bloque(int pos,TipoStruct* reg){
	unsigned int siguiente = pos;
	unsigned int tam_aux;
	bool metio = false;
	Bloque* bloque_actual = NULL;
	while( !metio ){
		bloque_actual = Buffer::get_instance()->get_bloque(*this, siguiente);
		tam_aux = bloque_actual->append(reg);
		if(tam_aux != 0)
			metio = true;
		else{
			siguiente = get_siguiente(bloque_actual);
			if(siguiente == bloque_actual->get_num_bloque() ){
				//creo un nuevo bloque y lo meto	
				Bloque * bloque_nuevo = obtener_bloque_nuevo();
				set_anterior(bloque_nuevo,bloque_actual->get_num_bloque());
				set_siguiente(bloque_actual,bloque_nuevo->get_num_bloque());
				set_siguiente(bloque_nuevo,bloque_nuevo->get_num_bloque());
				Buffer::get_instance()->guardar_bloque(*this,bloque_actual);
				delete bloque_actual;
				bloque_actual=bloque_nuevo;
				bloque_actual->append(reg);
				metio = true;
				espacios_libres->append(bloque_actual->get_espacio_libre());
			}
			else 
				delete bloque_actual;
		}
	}
	//actualizo el arch de espacios libres.
	espacios_libres->set_valor(bloque_actual->get_num_bloque(),bloque_actual->get_espacio_libre());
	Buffer::get_instance()->guardar_bloque(*this, bloque_actual);
	delete bloque_actual;
	
}

Bloque* ArchOverflow::obtener_bloque_nuevo(){
	int result = espacios_libres->consultar_espacio_libre(this->tam_bloque - TAM_TAG_SIG - TAM_TAG_ANT - 4);
	Bloque* bloque=NULL;
	if (result == -1){
		bloque = new Bloque(this->cant_bloques,this->tam_bloque);
		this->cant_bloques++;
	}
	else
		bloque = Buffer::get_instance()->get_bloque(*this,result);
	return bloque;	
} 

unsigned int ArchOverflow::get_primer_bloque_libre(){
	int result = espacios_libres->consultar_espacio_libre(this->tam_bloque - TAM_TAG_SIG - TAM_TAG_ANT - 4);
	if(result == -1){
		result = this->cant_bloques;
		Bloque * bloque_nuevo = new Bloque(this->cant_bloques,this->get_tam_bloque());
		set_anterior(bloque_nuevo,result);
		set_siguiente(bloque_nuevo,result);
		Buffer::get_instance()->guardar_bloque(*this, bloque_nuevo);
		this->cant_bloques++;
		delete bloque_nuevo;
	}
	return result;
}

unsigned int ArchOverflow::get_espacio_libre() throw(ExcepArchCerrado, ExcepLeerBloque){
	/*unsigned int tam_libre = 0;
	unsigned int cont = 0;
	Bloque* bloque_actual = NULL;
	
	while (cont < cant_bloques){ //recorro todos los bloques
		try{
			bloque_actual = Buffer::get_instance()->get_bloque(*this,cont);
			tam_libre += bloque_actual->get_espacio_libre();
		}catch (ExcepLeerBloque &e){
			if (bloque_actual != NULL) { 
				delete (bloque_actual); 
				bloque_actual = NULL;
			}
			throw e;	//la sigo propagando por ahora
		}
		if (bloque_actual != NULL) { 
			delete (bloque_actual); 
			bloque_actual = NULL;
		}
		cont++;
	}
	return tam_libre; */
	unsigned int tam_libre;
	
	tam_libre =  this->espacios_libres->get_espacio_libre_datos();
	
	return tam_libre ;
}
 		
 unsigned int ArchOverflow::get_cant_regs() throw(ExcepArchCerrado){
	
	unsigned int cont = 0;
	Bloque* bloque_actual;
	
	for(unsigned int i = 1; i < this->cant_bloques; i++ ){
		//obtengo el bloque nro i desde el buffer
		bloque_actual = Buffer::get_instance()->get_bloque(*this,i);
		//le pido al bloque la cant de registros contenidos en el mismo
		cont += get_cant_regs_bloque(bloque_actual);
		//Borro el bloque que me devolvio el buffer
		delete (bloque_actual);
	}
	return cont;

}
 		
unsigned int ArchOverflow::get_esp_libre_indice()  throw(ExcepArchCerrado, ExcepLeerBloque){
	return 0;
}
		 										
unsigned int ArchOverflow::get_cant_regs_indice() throw(ExcepArchCerrado){
	return 0;
}

long ArchOverflow::get_tam_arch_indice() throw(ExcepArchCerrado){
	return 0;	
}
 		
void ArchOverflow::set_cant_regs_bloque(Bloque* bloque, unsigned int cant){
	//Escribo el bloque en la pos 0 con la cant de regs
	bloque->escribir((char*)&cant, 0, sizeof(unsigned int));
}

void ArchOverflow::set_cant_bloques_header(unsigned int cant){
	//Obtengo el bloque header del archivo
	Bloque* bloque_header = Buffer::get_instance()->get_bloque(*this, 0);
	bloque_header->escribir((char*)&cant, 0, sizeof(unsigned int));
	//Guardo el bloque
	Buffer::get_instance()->guardar_bloque(*this, bloque_header);
	delete (bloque_header);
}
		
unsigned int ArchOverflow::get_tamanio_serializado(TipoStruct* reg){
	unsigned int tam_s_dato;
	char* s_dato; //Dato serializado
	//Serializo el dato y devuelvo el tamanio obtenido
	reg->serializar(&s_dato, tam_s_dato);
	delete[] (s_dato);
	return tam_s_dato;
}

TiposArchivo ArchOverflow::get_tipo_archivo(){
	return OVERFLOW_HASH;
}

unsigned int ArchOverflow::get_cant_regs_bloque(Bloque* bloque){
	unsigned int resultado;
	std::vector<TipoStruct*> regs;
	bloque->get_registros_desde (this->get_registro_base(), regs, TAM_TAG_ANT + TAM_TAG_SIG);
	resultado = regs.size();
	std::vector<TipoStruct*>::iterator it;
	for (it = regs.begin(); it != regs.end(); it++)
		delete (*it);
	return resultado;
}
